/**
 * 编译模板,
 */
class Compiler {
  // 获取 vue实例对象，并立即编译模板
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
    this.compile(this.el);
  }
  //编译模板，处理文本节点 和  元素节点
  compile(el) {
    let childNodes = el.childNodes;
    Array.from(childNodes).forEach(node => {
      // 判断节点的类型，并对 元素 和 文本节点进行相应的处理
      if (this.isElementNode(node)) {
        this.compileElement(node);
      } else if (this.isTextNode(node)) {
        this.compileText(node);
      }
      // 当 node 下还有子节点时，需要递归 compile 方法，处理。
      if (node.childNodes && node.childNodes.length) {
        this.compile(node);
      }
    })
  }
  // 编译元素节点
  /**
   * 处理指令 
   * 1. 循环所有的属性（attributes），判断属性是否为 vue的指令
   * 2. 对指令进行处理
   * 
   * @param {*} node 
   */
  compileElement(node) {
    Array.from(node.attributes).forEach(attr => {
      if (this.isDirective(attr.name || '')) {
        const attrName = attr.name.substr(2); // 绑定的指令
        const key = attr.value; // 获取到 绑定的属性名
        this.update(node, attrName, key); // 更新节点下的元素值
      }else{
        //绑定属性
      }
    })
  }

  // 编译文本节点，将插值表达式替换成 vue中绑定属性的值。
  /**
   * 这里介绍了如何去将视图中绑定的属性名替换为 vue中的对应的属性值
   * 1. 使用 正则匹配插值表达式，利用正则的分组获取到插值表达式中的 属性名
   * 2. 将文本节点中的插值表达式替换为 vue中的属性值。使用正则进行替换
   * @param {*} node 
   */
  compileText(node) {
    const reg = /\{\{(.+?)\}\}/; //匹配插值表达式中的 属性名,小括号有分组的含义
    const value = node.textContent;
    // 判断文本中是否 包含插值表达式
    if (reg.test(value)) {
      const key = RegExp.$1.trim(); //获取插值表达式中的属性名， $1 为 第一组
      // 将插值表达式替换成 对应的属性值
      node.textContent = value.replace(reg, this.vm[key]);
      
      // 在 首次编译后 ，创建一个监听器，监听当前依赖属性的变化，当属性发生变化后，立即更新
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue;
      })
    }
  }
  update(node, attrName, key) {
    let updateFn;
    //解析事件指令
    if (this.isEventDirective(attrName)) {
      const eventName = attrName.substr(3);
      this.onUpdater(node,key,eventName);
      return;
    } else {
      updateFn = this[attrName + 'Updater']
    }

    updateFn && updateFn.call(this, node, this.vm[key], key);
  }
  // 处理v-text指令， 将值取出来，赋予当前元素的 textContent 
  textUpdater(node, value, key) {
    node.textContent = value;
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue;
    })
  }
  // 处理 v-model 指令，将 vm 上的属性值 赋给 input 的value
  modelUpdater(node, value, key) {
    node.value = value;
    // 双向绑定中，改变input 框的值是通过 input 事件来实现的，在input的时候将值传递给vue实例中对应属性，
    // 赋值后又触发了 更新通知，因此订阅了这个属性的所有观察者收到更新消息，就执行了更新操作。
    node.addEventListener('input', () => {
      this.vm[key] = node.value;
    })
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue;
    })
  }
  // 实现 v-html指令
  htmlUpdater(node, value, key) {
    // 第一次编译时，执行设置innerHTML
    node.innerHTML = value;
    new Watcher(this.vm, key, (newValue) => {
      node.innerHTML = newValue; //当 key属性发生变化时，更新node对应的 innerHTML属性
    })
  }
  /**
   * 
   * @param {*} node 
   * @param {*} callback 事件的回调
   * @param {*} key vue中 methods 的属性名
   * @param {*} eventName  事件的名称
   */
  onUpdater(node,key,eventName) {
     // 判断回调中是否传入了参数
     const pattern = /\((.+?)\)/g;
     let params;
     let callbackName = key;
     if(pattern.test(key)){
       params = RegExp.$1.split(/',|",|,/g);
       callbackName = key.split('(')[0]; // 获取方法名
       params = params.map(vmKey => {
          const strRE = /'|''/g
          // 判断传入的值是字符串还是 vm属性
          if(strRE.test(vmKey) || this.isBoolean(vmKey) ||  this.isNumber(vmKey)){
              return {key: eval(vmKey)}
          }
          return {key: vmKey,isProperty: true}
       })
     }
     // 添加事件监听
      this.vm[callbackName] && node.addEventListener(eventName,(e) => {
        let  args = [e];
        //有参数的情况下,不会将 e作为参数，分两种情况，一种是有
        if(params){
          args = [];
          params.forEach(_item => {
            let val = _item.key;
            if(_item.isProperty){
             val = this.vm[_item.key];
            }
            args.push(val)
          })
        }
        this.vm[callbackName] && this.vm[callbackName](...args);
      });
  }
  isBoolean(text) {
    if(text === 'true' || text === 'false'){
      return true;
    }
    return false
  }
  isNumber(text){
    if(!isNaN(+text)){
      return true;
    }
    return false
  }
  //是否为事件指令
  isEventDirective(attrName) {
    return attrName.startsWith('on:')
  }
  // 判断元素上的属性是否为指令
  isDirective(attrName) {
    return attrName.startsWith('v-');
  }
  // 判断是否为 文本节点
  isTextNode(node) {
    return node.nodeType === 3;
  }
  // 判断是否为元素节点
  isElementNode(node) {
    return node.nodeType === 1;
  }

}